package com.jms.tomcat.util;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class PackageUtil {

    public static List<String> getClassName() throws IOException {
        return getClassNameByPackageName(true, (String) null);
    }

    public static List<String> getClassNameByPackageName(boolean searchChild, String ...packageName) throws IOException {
        List<String> classNames = new ArrayList<>();
        URL classURL;
        Enumeration<URL> classURLs = Thread.currentThread().getContextClassLoader().getResources("");
        if (classURLs != null) {
            while (classURLs.hasMoreElements()) {
                classURL = classURLs.nextElement();
                classNames.addAll(getClassNameByURL(classURL, searchChild, packageName));
            }
        } else {
            classURL = Thread.currentThread().getContextClassLoader().getResource("");
            if (classURL == null) {
                classURL = PackageUtil.class.getResource("");
            }
            if (classURL != null) {
                classNames.addAll(getClassNameByURL(classURL, searchChild, packageName));
            }
        }
        return classNames;
    }

    public static List<String> getClassNameByFilePath(String filePath, boolean searchChild, String ...packageName) {
        if (filePath == null) {
            throw new IllegalArgumentException("The filePath cannot be empty.");
        }
        return getClassNameByFile(new File(filePath), searchChild, packageName);
    }

    public static List<String> getClassNameByFile(File file, boolean searchChild, String ...packageName) {
        if (file == null || !file.exists()) {
            throw new IllegalArgumentException("The file does not exist.");
        }
        List<String> classNames = new ArrayList<>();
        if (file.isDirectory()) {
            if (searchChild) {
                File[] files = file.listFiles();
                if (files != null) {
                    for (File childFile : files) {
                        classNames.addAll(getClassNameByFile(childFile, true, packageName));
                    }
                }
            }
        } else if (file.getName().endsWith(".class")) {
            String className = convertClassPathToClassName(file.getAbsolutePath(), packageName);
            if (className != null) {
                classNames.add(className);
            }
        }
        return classNames;
    }

    public static List<String> getClassNameByJarPath(String jarPath, boolean searchChild, String ...packageName) throws IOException {
        if (jarPath == null) {
            throw new IllegalArgumentException("The jarPath cannot be empty.");
        }
        try (JarFile jarFile = new JarFile(jarPath)) {
            return getClassNameByJar(jarFile, searchChild, packageName);
        }
    }

    public static List<String> getClassNameByJar(JarFile jarFile, boolean searchChild, String ...packageName) {
        if (jarFile == null) {
            throw new IllegalArgumentException("The jar file does not exist.");
        }
        List<String> classNames = new ArrayList<>();
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            String entryName = entry.getName();
            if (entryName.endsWith(".class")) {
                String className = convertClassPathToClassName(entryName, packageName);
                if (className == null) {
                    continue;
                }
                if (searchChild) {
                    classNames.add(className);
                } else {
                    if (className.lastIndexOf(".") == -1) {
                        classNames.add(className);
                    }
                }
            }
        }
        return classNames;
    }

    public static List<String> getClassNameByURL(URL url, boolean searchChild, String ...packageName) throws IOException {
        String type = url.getProtocol();
        if ("file".equals(type)) {
            return getClassNameByFilePath(url.getPath(), searchChild, packageName);
        } else if ("jar".equals(type)) {
            String urlPath = url.getPath();
            String jarPath = urlPath.substring(urlPath.indexOf("/"), urlPath.indexOf("!"));
            return getClassNameByJarPath(jarPath, searchChild, packageName);
        } else {
            throw new IOException("Types of protocols that are not supported: " + type);
        }
    }

    private static String convertClassPathToClassName(String path, String ...packageName) {
        if (path.contains("classes")) {
            path = path.substring(path.indexOf("classes") + 8, path.lastIndexOf("."));
        } else {
            path = path.substring(0, path.lastIndexOf("."));
        }
        path = path.replace(File.separator, ".")
                .replace("/", ".");
        boolean flag = false;
        if (packageName != null) {
            for (String name : packageName) {
                if (path.startsWith(name)) {
                    flag = true;
                    break;
                }
            }
        } else {
            flag = true;
        }
        return flag ? path : null;
    }
}
